/**
 * This file is part of the ID2203 course assignments kit.
 * 
 * Copyright (C) 2009 Royal Institute of Technology (KTH)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package application3;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import application.ApplicationContinue;
import assignment1.eperfectfailuredetector.EPerfectFailureDetector;
import assignment1.eperfectfailuredetector.RestoreEvent;
import assignment1.eperfectfailuredetector.SuspectEvent;
import assignment1.perfectfailuredetector.CrashEvent;
import assignment1.perfectfailuredetector.PerfectFailureDetector;
import atomicRegister.AtomicRegister;
import atomicRegister.ReadRequest;
import atomicRegister.ReadResponse;
import atomicRegister.WriteRequest;
import atomicRegister.WriteResponse;
import se.kth.ict.id2203.application.Flp2pMessage;
import se.kth.ict.id2203.application.Pp2pMessage;
import se.kth.ict.id2203.flp2p.FairLossPointToPointLink;
import se.kth.ict.id2203.flp2p.Flp2pSend;
import se.kth.ict.id2203.pp2p.PerfectPointToPointLink;
import se.kth.ict.id2203.pp2p.Pp2pSend;
import se.sics.kompics.ComponentDefinition;
import se.sics.kompics.Handler;
import se.sics.kompics.Kompics;
import se.sics.kompics.Positive;
import se.sics.kompics.Start;
import se.sics.kompics.address.Address;
import se.sics.kompics.timer.ScheduleTimeout;
import se.sics.kompics.timer.Timer;

/**
 * The <code>Application0</code> class.
 * 
 * @author Cosmin Arad <cosmin@sics.se>
 * @version $Id: Application0.java 516 2009-01-28 04:00:47Z cosmin $
 */
public final class Application3 extends ComponentDefinition {

	Positive<AtomicRegister> areg = requires(AtomicRegister.class); 
	Positive<Timer> timer = requires(Timer.class);

	private static final Logger logger = LoggerFactory
			.getLogger(Application3.class);

	private String[] commands;
	private int lastCommand;
	private Set<Address> neighborSet;
	private Address self;

	/**
	 * Instantiates a new application0.
	 */
	public Application3() {
		subscribe(handleInit, control);
		subscribe(handleStart, control);
		subscribe(handleContinue, timer);
		subscribe(handleReadResponse, areg);
		subscribe(handleWriteResponse, areg);
	}

	Handler<Application3Init> handleInit = new Handler<Application3Init>() {
		public void handle(Application3Init event) {
			commands = event.getCommandScript().split(":");
			lastCommand = -1;
			neighborSet = event.getNeighborSet();
			self = event.getSelf();
		}
	};

	Handler<Start> handleStart = new Handler<Start>() {
		public void handle(Start event) {
			doNextCommand();
		}
	};

	Handler<ApplicationContinue> handleContinue = new Handler<ApplicationContinue>() {
		public void handle(ApplicationContinue event) {
			doNextCommand();
		}
	};

	
	Handler<ReadResponse> handleReadResponse = new Handler <ReadResponse>() {
		@Override
		public void handle(ReadResponse event) {
			logger.info("Received ReadResponse event for ({}, {})", event.getR(), event.getReadval());
		}	
	};
	
	Handler <WriteResponse> handleWriteResponse = new Handler<WriteResponse>() {
		@Override
		public void handle(WriteResponse event) {
			logger.info("Received WriteResponse event for ({})", event.getR());
		}	
	};
	
	
	private final void doNextCommand() {
		lastCommand++;

		if (lastCommand > commands.length) {
			return;
		}
		if (lastCommand == commands.length) {
			logger.info("DONE ALL OPERATIONS");
			Thread applicationThread = new Thread("ApplicationThread") {
				public void run() {
					BufferedReader in = new BufferedReader(
							new InputStreamReader(System.in));
					while (true) {
						try {
							String line = in.readLine();
							doCommand(line);
						} catch (Throwable e) {
							e.printStackTrace();
						}
					}
				}
			};
			applicationThread.start();
			return;
		}
		String op = commands[lastCommand];
		doCommand(op);
	}

	private void doCommand(String cmd) {
		if (cmd.startsWith("D")) {
			doSleep(Integer.parseInt(cmd.substring(1)));
		} else if (cmd.startsWith("R")) {
			doRead(0);
			doNextCommand();
		} else if (cmd.startsWith("W")){
			doWrite(0, Integer.parseInt(cmd.substring(1)));
			doNextCommand();
		} else if (cmd.startsWith("X")) {
			doShutdown();
		} else if (cmd.equals("help")) {
			doHelp();
			doNextCommand();
		} else {
			logger.info("Bad command: '{}'. Try 'help'", cmd);
			doNextCommand();
		}
	}
	
	private final void doRead(int r) {
		logger.info("Trigger read request");
		trigger (new ReadRequest(r), areg);
	}
	
	private final void doWrite(int r, int v) {
		trigger (new WriteRequest(r, v), areg);
	};
	
	private final void doHelp() {
		logger.info("Available commands: P<m>, L<m>, S<n>, help, X");
		logger.info("Pm: sends perfect message 'm' to all neighbors");
		logger.info("Lm: sends lossy message 'm' to all neighbors");
		logger.info("Sn: sleeps 'n' milliseconds before the next command");
		logger.info("help: shows this help message");
		logger.info("X: terminates this process");
	}
	
	private void doSleep(long delay) {
		logger.info("Sleeping {} milliseconds...", delay);

		ScheduleTimeout st = new ScheduleTimeout(delay);
		st.setTimeoutEvent(new ApplicationContinue(st));
		trigger(st, timer);
	}

	private void doShutdown() {
		System.out.println("2DIE");
		System.out.close();
		System.err.close();
		Kompics.shutdown();
	}
}
